package cn.lg.soar.system.biz.modules.general.service.impl;

import cn.lg.soar.common.util.DatetimeUtil;
import cn.lg.soar.core.sn.SnElement;
import cn.lg.soar.system.biz.modules.general.entity.SerialNumber;
import cn.lg.soar.system.biz.modules.general.mapper.SerialNumberMapper;
import cn.lg.soar.system.biz.modules.general.service.SerialNumberService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 基于数据库的流水号
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Service
public class SerialNumberServiceImpl implements SerialNumberService, SnElement {

	@Resource
	private SerialNumberMapper mapper;

	@Override
	public synchronized int incr(String key, LocalDateTime expire) {
		Integer value = mapper.getValue(key);
		if (value == null) {
			value = 1;
			SerialNumber serialNumberEntity = new SerialNumber();
			serialNumberEntity.setId(key);
			serialNumberEntity.setSerial(value);
			serialNumberEntity.setExpire(expire);
			int insert;
			try {
				insert = mapper.insert(serialNumberEntity);
			} catch (DuplicateKeyException e) {
				// 多线程重复插入时的重新调用
				if (e.getMessage().contains("for key 'bs_serial_number._key'")) {
					return incr(key, expire);
				} else {
					throw e;
				}
			}
			// 插入失败时重新调用
			if (insert != 1) {
				return incr(key, expire);
			}
		} else {
			// 数据库自增
			int oldValue = value;
			value += 1;
			if (mapper.incrValue(key, oldValue, value) != 1) {
				return incr(key, expire);
			}
		}
		return value;
	}

	@Scheduled(cron = "0 0 2 * * ?")
	public void clear() {
		// 删除失效的数据
		mapper.delete(
				Wrappers.<SerialNumber>lambdaQuery().le(SerialNumber::getExpire, LocalDateTime.now())
		);
	}

	@Override
	public String getKey() {
		return "dbSerial";
	}

	/**
	 * 例：dbSerial(4,$prefix,86400000)
	 * 方法说明：dbSerial(length, key, expire)
	 * @param length：流水位数
	 * @param key：流水key
	 * @param expire：流水有效期
	 * @return
	 */
	@Override
	public String getValue(Object... args) {
		Integer len = (Integer) args[0];
		String key = (String) args[1];
		Integer ex = (Integer) args[2];
		long serial = incr(key, DatetimeUtil.from(System.currentTimeMillis() + ex));
		String format = String.format("%0" + len + "d", serial);
		return format.substring(format.length() - len);
	}

}
